package util;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Base64;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Security;
import java.security.spec.X509EncodedKeySpec;

/**
 * SM2加密工具类
 * 该类主要用于使用 SM2 算法对明文进行加密操作。
 */
public class SM2EncryptionUtil {

    // 静态代码块，在类加载时执行，将 Bouncy Castle 安全提供者添加到 Java 安全系统中
    static {
        // Bouncy Castle 提供了丰富的加密算法实现，添加该提供者以便后续使用 SM2 等算法
        Security.addProvider(new BouncyCastleProvider());
    }

    public static String addVersionWithStringReplace(String xmlString, String version) {
        return xmlString.replace(
                "<componentMaintReport>",
                "<componentMaintReport version=\"" + version + "\">"
        );
    }
    /**
     * 加密方法
     * @param publicKeyBase64 以 Base64 编码形式传入的 SM2 公钥
     * @param plainText 待加密的明文内容
     * @return 返回经过 SM2 加密并进行 Base64 编码后的密文
     * @throws Exception 如果在加密过程中出现异常，如密钥解析错误、加密操作失败等
     */
    public static String encrypt(String publicKeyBase64, String plainText,String codeVersion) throws Exception {
        // 将JSON字符串转换为XML字符串
        ObjectMapper objectMapper = new ObjectMapper();
        XmlMapper xmlMapper = new XmlMapper();
        // 先将JSON字符串反序列化为Object，再序列化为XML
        Object jsonObject = objectMapper.readValue(plainText, Object.class);
        String xmlDataStr = xmlMapper.writeValueAsString(jsonObject);

        //添加版本
        String xmlData = addVersionWithStringReplace(xmlDataStr,codeVersion);

        // 将XML转为Base64编码
        String base64Xml = java.util.Base64.getEncoder().encodeToString(xmlData.getBytes("UTF-8"));
        // 将 Base64 编码的公钥解码为字节数组
        byte[] publicKeyBytes = Base64.decode(publicKeyBase64);
        // 获取一个椭圆曲线（EC）密钥工厂实例，使用 Bouncy Castle 作为提供者
        KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
        // 创建一个 X.509 编码的密钥规范对象，用于将公钥字节数组转换为 Java 公钥对象
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
        // 根据密钥规范生成 Java 公钥对象
        PublicKey publicKey = keyFactory.generatePublic(keySpec);

        // 获取 SM2 算法使用的椭圆曲线参数，"sm2p256v1" 是 SM2 算法指定的曲线名称
        X9ECParameters ecParameters = GMNamedCurves.getByName("sm2p256v1");
        // 创建椭圆曲线域参数对象，包含曲线、基点、阶数和余因子等信息
        ECDomainParameters domainParameters = new ECDomainParameters(
                ecParameters.getCurve(),
                ecParameters.getG(),
                ecParameters.getN(),
                ecParameters.getH());

        // 从 Java 公钥对象中获取椭圆曲线上的点
        java.security.spec.ECPoint w = ((java.security.interfaces.ECPublicKey) publicKey).getW();
        // 获取该点的 X 坐标
        BigInteger x = w.getAffineX();
        // 获取该点的 Y 坐标
        BigInteger y = w.getAffineY();
        // 使用椭圆曲线和坐标创建 Bouncy Castle 的 ECPoint 对象
        ECPoint q = domainParameters.getCurve().createPoint(x, y);

        // 创建 Bouncy Castle 的椭圆曲线公钥参数对象
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(q, domainParameters);

        // 创建 SM2 加密引擎实例
        SM2Engine engine = new SM2Engine();
        // 初始化加密引擎，设置为加密模式，并结合随机数参数
        engine.init(true, new ParametersWithRandom(publicKeyParameters, new java.security.SecureRandom()));

        // 将明文转换为 UTF-8 编码的字节数组
        byte[] input = base64Xml.getBytes("UTF-8");
        // 使用加密引擎对字节数组进行加密操作
        byte[] encryptedBytes = engine.processBlock(input, 0, input.length);
        // 将加密后的字节数组进行 Base64 编码并返回
        return java.util.Base64.getEncoder().encodeToString(encryptedBytes);
    }
}